﻿using System;
using System.Collections.Generic;
using System.Timers;
using SharedTypes;
using System.IO;
using System.Collections;
using AdaptationProject.Forms;

namespace GeneticAlgorithm
{
    /// <summary>
    /// Класс, реализующий метод адаптации, основанный на 
    /// генетическом алгоритме.
    /// </summary>
    [AlgInfo("Genetic algorithm")]
    public class GeneticAlgorithm : IAdoptationMethod
    {
        //private TimeSpan elapsedTime; 
        
        /// <summary>
        /// Свойство, позволяющее пользователю задавать максимальное поколение.
        /// Снабжено специальным атрибутом, для отображения в окне настроек программы.
        /// </summary>
        [Parameter(typeof(Int32), GeneticAlgorithmSettings.MaxGenOrder, 5, 40, "Максимальное поколение")]
        public int MaxGeneration { get; set; }

        /// <summary>
        /// Свойство, позволяющее пользователю задавать количество особей в популяции.
        /// Снабжено специальным атрибутом, для отображения в окне настроек программы.
        /// </summary>
        [Parameter(typeof(Int32), GeneticAlgorithmSettings.PopulationOrder, 5, 30, "Количество особей в популяции")]
        public int PopulationAmount { get; set; }

        /// <summary>
        /// Конструктор по умолчанию. Задаёт значение максимального поколения и количества особей
        /// в популяции по умолчания (из файла настроек).
        /// </summary>
        public GeneticAlgorithm()
            : this(GeneticAlgorithmSettings.MaxGeneration, GeneticAlgorithmSettings.PopulationAmount)
        { }

        /// <summary>
        /// Конструктор.
        /// </summary>
        /// <param name="maxGen">Максимальное поколение.</param>
        /// <param name="population">Максимальное количество особей в поколении.</param>
        public GeneticAlgorithm(int maxGen, int population)
        {
            MaxGeneration = maxGen;
            PopulationAmount = population;
        }

        /// <summary>
        /// Обработчик события прогресса.
        /// </summary>
        public event EventHandler<ProgressEventArgs> ProgressEventHandler;

        private IList<int> GetNumbersSubTree(IDictionary<int, LinkedParameter> parameters)
        {
            IList<int> numbers = new List<int>();
            for (int i = 0; i < parameters.Count; ++i)
            {
                if (parameters[i].parentKey == -1)
                {
                    numbers.Add(i);
                }
            }

            return numbers;
        }

        private void GetSubTree(int index,
                                IDictionary<int, LinkedParameter> parameters,
                                int upToZero,
                                ref IDictionary<int, LinkedParameter> outParameters)
        {
            for (int i = 0; i < parameters.Count; ++i)
            {
                if (index == parameters[i].parentKey)
                {
                    LinkedParameter tmp = new LinkedParameter(parameters[i].parentKey - upToZero, parameters[i].parameter);
                    outParameters.Add(i - upToZero, tmp);
                    GetSubTree(i, parameters, upToZero, ref outParameters);
                }
            }
        }

        private IDictionary<int, LinkedParameter> GetNextParameters(int numSubTree, IDictionary<int, LinkedParameter> parameters)
        {
            IDictionary<int, LinkedParameter> outParameters = new Dictionary<int, LinkedParameter>();
            outParameters.Add(0, parameters[numSubTree]);
            GetSubTree(numSubTree, parameters, numSubTree == 0 ? 0 : numSubTree, ref outParameters);
            return outParameters;
        }

        private void CalculateSup(ReportInformationContainer forEverySubTreeCur)
        {
            forEverySubTreeCur.SupCompression.Add((forEverySubTreeCur.recommendedResult - forEverySubTreeCur.bestSize) 
                                                    /
                                                    Math.Max(forEverySubTreeCur.recommendedResult, forEverySubTreeCur.bestSize));
             forEverySubTreeCur.SupTime.Add(
                 (double)(forEverySubTreeCur.RecommendedResultTime.Ticks - forEverySubTreeCur.TotalElapsedTime.Ticks) 
                 /
                 (double)Math.Max(forEverySubTreeCur.RecommendedResultTime.Ticks, forEverySubTreeCur.TotalElapsedTime.Ticks));
        }

        private void CalculateReg(ReportInformationContainer forEverySubTreeCur)
        {
            double tmp = Math.Sign(forEverySubTreeCur.recommendedResult - forEverySubTreeCur.bestSize);
            if (tmp < 0) 
                tmp = 0;
            forEverySubTreeCur.RegCompression.Add(tmp);
            tmp = Math.Sign((double)(forEverySubTreeCur.RecommendedResultTime.Ticks - forEverySubTreeCur.TotalElapsedTime.Ticks));
            if (tmp < 0)
                tmp = 0;
            forEverySubTreeCur.RegTime.Add(tmp);
        }

        /// <summary>
        /// Запускает процесс адаптации выбранного алгоритма.
        /// </summary>
        /// <param name="alg">Адапритруемый алгоритм.</param>
        /// <param name="reportInfo">Объект отчёта.</param>
        public void Adopt(IAlgorithm alg, ref ReportInformationContainer reportInfo)
        {
            ReportInformationContainer forEverySubTreeBest = reportInfo;
            forEverySubTreeBest.SetMaxValues();
            IList<int> numbresSubTreeInTree = GetNumbersSubTree(alg.Parameters);

            // Проход по каждой ветке в деререве
            for (int k = 0; k < numbresSubTreeInTree.Count; ++k)
            {
                ReportInformationContainer forEverySubTreeCur = new ReportInformationContainer();
                forEverySubTreeCur.CopyHere(reportInfo, false);

                // Берем первую / следующую ветку из дерева параметров
                IDictionary<int, LinkedParameter> parameters = GetNextParameters(numbresSubTreeInTree[k],
                                                                                 alg.Parameters);

                //elapsedTime = new TimeSpan();
                
                // Получение рекомендуемых параметров
                long currentCompressionLevel = alg.GetResultWithRecomendedParameters(parameters);
                forEverySubTreeCur.RecommendedResultTime = alg.TimeElapsed;
                Population pop = new Population(PopulationAmount, alg, parameters, currentCompressionLevel);

                DateTime startAdapt = DateTime.Now;
                for (int i = 0; i < MaxGeneration; ++i)
                {
                    InvokeProgressEvent((1000000 / MaxGeneration) / (int)forEverySubTreeCur.ExperimentCount / (numbresSubTreeInTree.Count));
                    // Нахождение рациональных параметров
                    pop.GoToNextGeneration();
                }
                DateTime endAdapt = DateTime.Now;

                // выбор лучших значений
                Chromosome bestResult = pop.GetBestResult;
                if (bestResult.Fitness > forEverySubTreeCur.bestFitness)
                {
                    forEverySubTreeCur.bestFitness = bestResult.Fitness;
                    forEverySubTreeCur.FindedParameters = bestResult.Gens;
                }

                forEverySubTreeCur.recommendedResult = pop.ManualResult;
                forEverySubTreeCur.ElapsedTime = pop.GetLastGenTime();

                //Время поиска рациональных параметров
                reportInfo.SearchingTime += forEverySubTreeCur.TotalElapsedTime.Add(endAdapt.TimeOfDay.Subtract(startAdapt.TimeOfDay));
                
                // Время выполнения с подобраннымим параметрами
                forEverySubTreeCur.TotalElapsedTime = bestResult.TimeElapsed;
                forEverySubTreeCur.MethodName = "Genetic algorithm";

                // Калькуляция SR-оценки
                CalculateSup(forEverySubTreeCur);
                CalculateReg(forEverySubTreeCur);

                // выбор лучшей ветки из дерева параметров
                if (forEverySubTreeBest.bestSize > forEverySubTreeCur.bestSize)
                {
                    forEverySubTreeBest = forEverySubTreeCur;
                }
                InvokeProgressEvent((1000000 / MaxGeneration) / (int)forEverySubTreeCur.ExperimentCount / (numbresSubTreeInTree.Count));
            }
            reportInfo.CopyHere(forEverySubTreeBest, true);
        }

        /// <summary>
        /// Добавляет к пути.
        /// </summary>
        /// <param name="alg">Параметры с файлом.</param>
        void PathCatFile(string fileName, ref IDictionary<int, LinkedParameter> parameters)
        {
            for (int i = 0; i < parameters.Count; ++i)
            {
                if (parameters[i].parameter.Type == ParamType.PATH)
                {
                    string CurrentValue =
                        parameters[i].parameter.CurrentValue.ToString() +
                        fileName;

                    string DefaultValue =
                        parameters[i].parameter.DefaultValue.ToString() +
                        fileName;

                    string Name =
                        parameters[i].parameter.Name.ToString() +
                        fileName;

                    IList<AcceptableValue> acceptValues = new List<AcceptableValue>();
                    AcceptableValue acceptValue = new AcceptableValue();
                    acceptValue = parameters[i].parameter.AcceptableValues[0];
                    acceptValue.TextValues = CurrentValue;
                    acceptValue.values = CurrentValue;
                    acceptValues.Add(acceptValue);

                    CmdParameter param = new CmdParameter(parameters[i].parameter.OrderNum,
                                                          Name,
                                                          parameters[i].parameter.ConditionLink,
                                                          parameters[i].parameter.ShortDescription,
                                                          parameters[i].parameter.Description,
                                                          acceptValues,
                                                          parameters[i].parameter.ValueEnd,
                                                          parameters[i].parameter.Separator,
                                                          DefaultValue,
                                                          parameters[i].parameter.IsParamKey,
                                                          parameters[i].parameter.Type);
                    param.CurrentValue = CurrentValue;

                    parameters[i].parameter = param;

                    break;
                }
            }
            
        }
        
        /// <summary>
        /// Запускает процесс адаптации выбранного алгоритма для файла и рекомендуемыми и найденными параметрами.
        /// </summary>
        /// <param name="alg">Адапритруемый алгоритм.</param>
        /// <param name="reportInfo">Объект отчёта в виде найденных рациональных параметров.</param>
        /// <param name="reportInfo">Объект отчёта в виде информации об конкретном файле.</param>
        public void CompressFileByParams(IAlgorithm alg,
                                         ReportInformationContainer foundBestParameters,
                                         ref ReportInformationContainer reportCurrentFile)
        {
            IList<int> numbresSubTreeInTree = GetNumbersSubTree(alg.Parameters);
            IDictionary<int, LinkedParameter> recomendParameters = GetNextParameters(numbresSubTreeInTree[0],
                                                                                     alg.Parameters);
            // Формируем путь с файлом
            PathCatFile(reportCurrentFile.FileNames[0].Name, ref recomendParameters);

            // Архивируем
            long recommendedResultCompressionLevel = alg.GetResultWithRecomendedParameters(recomendParameters);

            // Сохраняем рузельтаты
            reportCurrentFile.RecommendedResultTime = alg.TimeElapsed;
            reportCurrentFile.recommendedResult = recommendedResultCompressionLevel;

            // Делаем глубокую копию
            IDictionary<int, LinkedParameter> findedParameters = new Dictionary<int, LinkedParameter>();
            foreach (KeyValuePair<int, LinkedParameter> parameter in reportCurrentFile.FindedParameters)
            {
                KeyValuePair<int, LinkedParameter> value = new KeyValuePair<int, LinkedParameter>(parameter.Key, new LinkedParameter(parameter.Value.parentKey, parameter.Value.parameter));
                findedParameters.Add(value);
            }

            // Формируем путь с файлом
            PathCatFile(reportCurrentFile.FileNames[0].Name, ref findedParameters);

            DateTime startAdapt = DateTime.Now;
            double currFitness = 0;
            // Архивируем
            long bestResultCompressionLevel = alg.DoCompression(findedParameters);
            if (bestResultCompressionLevel > 0)
            {
                currFitness = recommendedResultCompressionLevel / (double)bestResultCompressionLevel;
            }
            else
            {
                currFitness = 0;
            }
            DateTime endAdapt = DateTime.Now;

            // Сохраняем рузельтаты
            reportCurrentFile.bestFitness = currFitness;
            //reportCurrentFile.bestSize = bestResultCompressionLevel;

            // Время выполнения с подобраннымим параметрами
            reportCurrentFile.TotalElapsedTime = endAdapt - startAdapt;
            reportCurrentFile.MethodName = "Genetic algorithm";

            CalculateSup(reportCurrentFile);
            CalculateReg(reportCurrentFile);
        }

        /// <summary>
        /// Возвращает размер директории.
        /// </summary>
        /// <param name="userSettings">Путь к директории.</param>
        float CalculateFolderSize(string folder)
        {
            float folderSize = 0.0f;
            try
            {
                //Checks if the path is valid or not
                if (!Directory.Exists(folder))
                {
                    return folderSize;
                }
                else
                {
                    try
                    {
                        foreach (string file in Directory.GetFiles(folder))
                        {
                            if (File.Exists(file))
                            {
                                FileInfo finfo = new FileInfo(file);
                                folderSize += finfo.Length;
                            }
                        }

                        foreach (string dir in Directory.GetDirectories(folder))
                            folderSize += CalculateFolderSize(dir);
                    }
                    catch (NotSupportedException e)
                    {
                        Console.WriteLine("Unable to calculate folder size: {0}", e.Message);
                    }
                }
            }
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine("Unable to calculate folder size: {0}", e.Message);
            }
            return folderSize;
        }

        /// <summary>
        /// Устанавливает параметры, заданные пользователем (порядок должен совпадать).
        /// </summary>
        /// <param name="userSettings">Список пользовательских параметров.</param>
        public bool SetParameters(List<object> userSettings)
        {
            bool result = true;
            try
            {
                MaxGeneration = (int) userSettings[GeneticAlgorithmSettings.MaxGenOrder];
                PopulationAmount = (int)userSettings[GeneticAlgorithmSettings.PopulationOrder];
                GeneticAlgorithmSettings.MaxGeneration = MaxGeneration;
                GeneticAlgorithmSettings.PopulationAmount = PopulationAmount;
            }
            catch (Exception)
            {
                result = false;
            }

            return result;
        }

        /// <summary>
        /// Возвращает параметры, заданные пользователем (порядок должен совпадать).
        /// </summary>
        /// <param name="userSettings">Список пользовательских параметров.</param>
        public bool GetParameters(out List<object> userSettings)
        {
            bool result = true;
            try
            {
                userSettings = new List<object>();
                userSettings.Insert(GeneticAlgorithmSettings.MaxGenOrder, MaxGeneration);
                userSettings.Insert(GeneticAlgorithmSettings.PopulationOrder, PopulationAmount);
            }
            catch (Exception)
            {
                result = false;
                userSettings = null;
            }

            return result;
        }

        /// <summary>
        /// Вызывает всех подписчиков на событие прогресса.
        /// </summary>
        /// <param name="progress">Текущий прогресс (в процентах).</param>
        private void InvokeProgressEvent(int progress)
        {
            ProgressEventHandler.Invoke(this, new ProgressEventArgs(progress));
        }
    }
}
